#include "sdk.h"
#include "ccmanager.h"
#include <wx/html/htmlwin.h>
#include <wx/display.h>
#include "M_Code_colourmanager.h"
#include "M_Code_StyledTextCtrl.h"
#include "editor_hooks.h"

#define CALLTIP_REFRESH_DELAY 90
#define AUTOCOMP_SELECT_DELAY 35
#define SCROLL_REFRESH_DELAY 500
#define FROM_TIMER 1

namespace CCManagerHelper {
  inline void RipplePts( int& ptA, int& ptB, int len, int delta ) {
    if( ptA > len - delta ) {
      ptA += delta;
    }
    if( ptB > len - delta ) {
      ptB += delta;
    }
  }
  inline int FindColumn( int line, int column, wxScintilla* stc ) {
    int lnEnd = stc->GetLineEndPosition( line );
    for( int pos = stc->PositionFromLine( line ); pos < lnEnd; ++pos ) {
      if( stc->GetColumn( pos ) == column ) {
        return pos;
      }
    }
    return lnEnd;
  }
  inline bool IsPosVisible( int pos, wxScintilla* stc ) {
    const int dist = stc->VisibleFromDocLine( stc->LineFromPosition( pos ) ) - stc->GetFirstVisibleLine();
    return !( dist < 0 || dist > stc->LinesOnScreen() );
  }
  static int CallTipToInt( const wxString& firstTip, int numPages ) {
    int val = 33 * firstTip.Length() ^ numPages;
    for( wxString::const_iterator itr = firstTip.begin();
         itr != firstTip.end(); ++itr ) {
      val = 33 * val ^ static_cast<int>( *itr );
    }
    return val;
  }
  static void BuildFontSizes( int *sizes, int size ) {
    sizes[0] = int( size * 0.75 );
    sizes[1] = int( size * 0.83 );
    sizes[2] = size;
    sizes[3] = int( size * 1.2 );
    sizes[4] = int( size * 1.44 );
    sizes[5] = int( size * 1.73 );
    sizes[6] = int( size * 2 );
  }
  static int GetDefaultHTMLFontSize() {
    int size = wxNORMAL_FONT->GetPointSize();
    if( size < 9 ) {
      size = 9;
    }
    return size;
  }
}

template<> CCManager* Mgr<CCManager>::instance = nullptr;
template<> bool Mgr<CCManager>::isShutdown = false;

const int idCallTipTimer = wxNewId();
const int idAutoLaunchTimer = wxNewId();
const int idAutocompSelectTimer = wxNewId();
const int idShowTooltip = XRCID( "idShowTooltip" );
const int idCallTipNext = XRCID( "idCallTipNext" );
const int idCallTipPrevious = XRCID( "idCallTipPrevious" );

DEFINE_EVENT_TYPE( cbEVT_DEFERRED_CALLTIP_SHOW )
DEFINE_EVENT_TYPE( cbEVT_DEFERRED_CALLTIP_CANCEL )

enum TooltipMode {
  tmDisable = 0,
  tmEnable,
  tmForceSinglePage,
  tmKeyboundOnly
};

enum ACLaunchState {
  lsTknStart,
  lsCaretStart
};

class UnfocusablePopupWindow :
  public wxPopupWindow {
  public:
    typedef wxPopupWindow BaseClass;
    UnfocusablePopupWindow( wxWindow* parent, int style = wxBORDER_NONE ) :
      wxPopupWindow( parent, style ) {
      Hide();
    }
    bool Destroy();
    void OnFocus( wxFocusEvent& event );
    void ActivateParent();
    virtual void DoSetSize( int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO );
    virtual bool Show( bool show = true );
  private:
    DECLARE_EVENT_TABLE()
};

bool UnfocusablePopupWindow::Destroy() {
  if( !wxPendingDelete.Member( this ) ) {
    wxPendingDelete.Append( this );
  }
  return true;
}

void UnfocusablePopupWindow::OnFocus( wxFocusEvent& event ) {
  ActivateParent();
  GetParent()->SetFocus();
  event.Skip();
}

void UnfocusablePopupWindow::ActivateParent() {
  wxTopLevelWindow *frame = wxDynamicCast( wxGetTopLevelParent( GetParent() ), wxTopLevelWindow );
  if( frame ) {
    frame->Raise();
  }
}

void UnfocusablePopupWindow::DoSetSize( int x, int y, int width, int height, int sizeFlags ) {
  if( x != wxDefaultCoord ) {
    GetParent()->ClientToScreen( &x, NULL );
  }
  if( y != wxDefaultCoord ) {
    GetParent()->ClientToScreen( NULL, &y );
  }
  BaseClass::DoSetSize( x, y, width, height, sizeFlags );
}

bool UnfocusablePopupWindow::Show( bool show ) {
  bool rv = BaseClass::Show( show );
  if( rv && show ) {
    ActivateParent();
  }
  return rv;
}

BEGIN_EVENT_TABLE( UnfocusablePopupWindow, UnfocusablePopupWindow::BaseClass )
  EVT_SET_FOCUS( UnfocusablePopupWindow::OnFocus )
END_EVENT_TABLE()

CCManager::CCManager() :
  m_AutocompPosition( wxSCI_INVALID_POSITION ),
  m_CallTipActive( wxSCI_INVALID_POSITION ),
  m_LastAutocompIndex( wxNOT_FOUND ),
  m_LastTipPos( wxSCI_INVALID_POSITION ),
  m_WindowBound( 0 ),
  m_OwnsAutocomp( true ),
  m_CallTipTimer( this, idCallTipTimer ),
  m_AutoLaunchTimer( this, idAutoLaunchTimer ),
  m_AutocompSelectTimer( this, idAutocompSelectTimer ),
  m_pLastEditor( nullptr ),
  m_pLastCCPlugin( nullptr ) {
  const wxString ctChars = _T( ",;\n()" );
  m_CallTipChars[nullptr] = std::set<wxChar>( ctChars.begin(), ctChars.end() );
  const wxString alChars = _T( ".:<>\"#/" );
  m_AutoLaunchChars[nullptr] = std::set<wxChar>( alChars.begin(), alChars.end() );
  m_LastACLaunchState[lsCaretStart] = wxSCI_INVALID_POSITION;
  m_pPopup = new UnfocusablePopupWindow( Manager::Get()->GetAppFrame() );
  m_pHtml = new wxHtmlWindow( m_pPopup, wxID_ANY, wxDefaultPosition,
                              wxDefaultSize, wxHW_SCROLLBAR_AUTO | wxBORDER_SIMPLE );
  int sizes[7] = {};
  CCManagerHelper::BuildFontSizes( sizes, CCManagerHelper::GetDefaultHTMLFontSize() );
  m_pHtml->SetFonts( wxEmptyString, wxEmptyString, &sizes[0] );
  m_pHtml->Connect( wxEVT_COMMAND_HTML_LINK_CLICKED,
                    wxHtmlLinkEventHandler( CCManager::OnHtmlLink ), nullptr, this );
  ColourManager* cmgr = Manager::Get()->GetColourManager();
  cmgr->RegisterColour( _T( "Code completion" ), _T( "Tooltip/Calltip background" ), _T( "cc_tips_back" ), *wxWHITE );
  cmgr->RegisterColour( _T( "Code completion" ), _T( "Tooltip/Calltip foreground" ), _T( "cc_tips_fore" ), wxColour( _T( "DIM GREY" ) ) );
  cmgr->RegisterColour( _T( "Code completion" ), _T( "Tooltip/Calltip highlight" ), _T( "cc_tips_highlight" ), wxColour( _T( "BLUE" ) ) );
  wxFrame* mainFrame = Manager::Get()->GetAppFrame();
  mainFrame->Connect( idShowTooltip, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CCManager::OnMenuSelect ), nullptr, this );
  mainFrame->Connect( idCallTipNext, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CCManager::OnMenuSelect ), nullptr, this );
  mainFrame->Connect( idCallTipPrevious, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CCManager::OnMenuSelect ), nullptr, this );
  typedef cbEventFunctor<CCManager, CodeBlocksEvent> CCEvent;
  Manager::Get()->RegisterEventSink( cbEVT_APP_DEACTIVATED, new CCEvent( this, &CCManager::OnDeactivateApp ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_DEACTIVATED, new CCEvent( this, &CCManager::OnDeactivateEd ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_OPEN, new CCEvent( this, &CCManager::OnEditorOpen ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_CLOSE, new CCEvent( this, &CCManager::OnEditorClose ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_TOOLTIP, new CCEvent( this, &CCManager::OnEditorTooltip ) );
  Manager::Get()->RegisterEventSink( cbEVT_SHOW_CALL_TIP, new CCEvent( this, &CCManager::OnShowCallTip ) );
  Manager::Get()->RegisterEventSink( cbEVT_COMPLETE_CODE, new CCEvent( this, &CCManager::OnCompleteCode ) );
  m_EditorHookID = EditorHooks::RegisterHook( new EditorHooks::HookFunctor<CCManager>( this, &CCManager::OnEditorHook ) );
  Connect( idCallTipTimer, wxEVT_TIMER, wxTimerEventHandler( CCManager::OnTimer ) );
  Connect( idAutoLaunchTimer, wxEVT_TIMER, wxTimerEventHandler( CCManager::OnTimer ) );
  Connect( idAutocompSelectTimer, wxEVT_TIMER, wxTimerEventHandler( CCManager::OnTimer ) );
  Connect( cbEVT_DEFERRED_CALLTIP_SHOW, wxCommandEventHandler( CCManager::OnDeferredCallTipShow ) );
  Connect( cbEVT_DEFERRED_CALLTIP_CANCEL, wxCommandEventHandler( CCManager::OnDeferredCallTipCancel ) );
}

CCManager::~CCManager() {
  m_pHtml->Disconnect( wxEVT_COMMAND_HTML_LINK_CLICKED,
                       wxHtmlLinkEventHandler( CCManager::OnHtmlLink ), nullptr, this );
  m_pHtml->Destroy();
  m_pPopup->Destroy();
  wxFrame* mainFrame = Manager::Get()->GetAppFrame();
  mainFrame->Disconnect( idShowTooltip, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CCManager::OnMenuSelect ), nullptr, this );
  mainFrame->Disconnect( idCallTipNext, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CCManager::OnMenuSelect ), nullptr, this );
  mainFrame->Disconnect( idCallTipPrevious, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CCManager::OnMenuSelect ), nullptr, this );
  Manager::Get()->RemoveAllEventSinksFor( this );
  EditorHooks::UnregisterHook( m_EditorHookID, true );
  Disconnect( idCallTipTimer );
  Disconnect( idAutoLaunchTimer );
  Disconnect( idAutocompSelectTimer );
  Disconnect( cbEVT_DEFERRED_CALLTIP_SHOW );
  Disconnect( cbEVT_DEFERRED_CALLTIP_CANCEL );
}

cbCodeCompletionPlugin* CCManager::GetProviderFor( M_Code_Editor* ed ) {
  if( !ed ) {
    ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  }
  if( ed == m_pLastEditor ) {
    return m_pLastCCPlugin;
  }
  m_pLastEditor = ed;
  m_pLastCCPlugin = nullptr;
  m_LastACLaunchState[lsCaretStart] = wxSCI_INVALID_POSITION;
  const PluginsArray& pa = Manager::Get()->GetPluginManager()->GetCodeCompletionOffers();
  for( size_t i = 0; i < pa.GetCount(); ++i ) {
    cbCodeCompletionPlugin::CCProviderStatus status = static_cast<cbCodeCompletionPlugin*>( pa[i] )->GetProviderStatusFor( ed );
    if( status == cbCodeCompletionPlugin::ccpsActive ) {
      m_pLastCCPlugin = static_cast<cbCodeCompletionPlugin*>( pa[i] );
      break;
    } else if( status == cbCodeCompletionPlugin::ccpsUniversal ) {
      m_pLastCCPlugin = static_cast<cbCodeCompletionPlugin*>( pa[i] );
    }
  }
  return m_pLastCCPlugin;
}

void CCManager::RegisterCallTipChars( const wxString& chars, cbCodeCompletionPlugin* registrant ) {
  if( registrant ) {
    m_CallTipChars[registrant] = std::set<wxChar>( chars.begin(), chars.end() );
  }
}

void CCManager::RegisterAutoLaunchChars( const wxString& chars, cbCodeCompletionPlugin* registrant ) {
  if( registrant ) {
    m_AutoLaunchChars[registrant] = std::set<wxChar>( chars.begin(), chars.end() );
  }
}

void CCManager::NotifyDocumentation() {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( ed ) {
    DoShowDocumentation( ed );
  }
}

void CCManager::NotifyPluginStatus() {
  m_pLastEditor = nullptr;
  m_pLastCCPlugin = nullptr;
}

void CCManager::InjectAutoCompShow( int lenEntered, const wxString& itemList ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( ed ) {
    ed->GetControl()->AutoCompShow( lenEntered, itemList );
    m_OwnsAutocomp = false;
    m_AutocompTokens.clear();
  }
}


void CCManager::AdvanceTip( Direction direction ) {
  if( direction == Next ) {
    ++m_CurCallTip;
    if( m_CurCallTip == m_CallTips.end() ) {
      m_CurCallTip = m_CallTips.begin();
    }
  } else {
    if( m_CurCallTip == m_CallTips.begin() ) {
      if( m_CallTips.size() > 1 ) {
        m_CurCallTip = m_CallTips.begin() + m_CallTips.size() - 1;
      }
    } else
    { --m_CurCallTip; }
  }
}

bool CCManager::ProcessArrow( int key ) {
  bool wasProcessed = false;
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !ed ) {
    return wasProcessed;
  }
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  if( stc->CallTipActive() && m_CallTipActive != wxSCI_INVALID_POSITION && m_CallTips.size() > 1 ) {
    if( key == WXK_DOWN ) {
      AdvanceTip( Next );
    } else if( key == WXK_UP ) {
      AdvanceTip( Previous );
    } else
    { return wasProcessed; }
    DoUpdateCallTip( ed );
    wasProcessed = true;
  }
  return wasProcessed;
}

struct TokenSorter {
  bool& m_PureAlphabetical;
  bool m_CaseSensitive;
  TokenSorter( bool& alphabetical, bool caseSensitive ): m_PureAlphabetical( alphabetical ), m_CaseSensitive( caseSensitive ) {
    m_PureAlphabetical = true;
  }
  bool operator()( const cbCodeCompletionPlugin::CCToken& a, const cbCodeCompletionPlugin::CCToken& b ) {
    int diff = a.weight - b.weight;
    if( diff == 0 ) {
      if( m_CaseSensitive ) {
        diff = a.displayName.Cmp( b.displayName );
      } else {
        diff = a.displayName.Upper().Cmp( b.displayName.Upper() );
        if( diff == 0 ) {
          diff = a.displayName.Cmp( b.displayName );
        }
      }
    } else
    { m_PureAlphabetical = false; }
    return diff < 0;
  }
};

void CCManager::OnCompleteCode( CodeBlocksEvent& event ) {
  event.Skip();
  ConfigManager* cfg = Manager::Get()->GetConfigManager( _T( "ccmanager" ) );
  if( !cfg->ReadBool( _T( "/code_completion" ), true ) ) {
    return;
  }
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !ed ) {
    return;
  }
  cbCodeCompletionPlugin* ccPlugin = GetProviderFor( ed );
  if( !ccPlugin ) {
    return;
  }
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  int tknEnd = stc->GetCurrentPos();
  if( tknEnd == m_LastACLaunchState[lsCaretStart] && !m_AutocompTokens.empty() ) {
    DoBufferedCC( stc );
    return;
  }
  int tknStart = stc->WordStartPosition( tknEnd, true );
  m_AutocompTokens = ccPlugin->GetAutocompList( event.GetInt() == FROM_TIMER, ed, tknStart, tknEnd );
  if( m_AutocompTokens.empty() ) {
    return;
  }
  if( m_AutocompTokens.size() == 1 && cfg->ReadBool( _T( "/auto_select_single" ), false ) ) {
    if( !stc->CallTipActive() && !stc->AutoCompActive() ) {
      m_CallTipActive = wxSCI_INVALID_POSITION;
    }
    m_OwnsAutocomp = true;
    m_LastACLaunchState[lsTknStart] = tknStart;
    m_LastACLaunchState[lsCaretStart] = tknEnd;
    m_LastAutocompIndex = 0;
    wxScintillaEvent autoCompFinishEvt( wxEVT_SCI_AUTOCOMP_SELECTION );
    autoCompFinishEvt.SetText( m_AutocompTokens.front().displayName );
    OnEditorHook( ed, autoCompFinishEvt );
    return;
  }
  bool isPureAlphabetical = true;
  bool isCaseSensitive = cfg->ReadBool( _T( "/case_sensitive" ), false );
  TokenSorter sortFunctor( isPureAlphabetical, isCaseSensitive );
  std::sort( m_AutocompTokens.begin(), m_AutocompTokens.end(), sortFunctor );
  if( isPureAlphabetical ) {
    stc->AutoCompSetOrder( wxSCI_ORDER_PRESORTED );
  } else {
    stc->AutoCompSetOrder( wxSCI_ORDER_CUSTOM );
  }
  wxString items;
  items.Alloc( m_AutocompTokens.size() * 20 );
  for( size_t i = 0; i < m_AutocompTokens.size(); ++i ) {
    items += m_AutocompTokens[i].displayName;
    if( m_AutocompTokens[i].category == -1 ) {
      items += _T( "\r" );
    } else
    { items += F( _T( "\n%d\r" ), m_AutocompTokens[i].category ); }
  }
  items.RemoveLast();
  if( !stc->CallTipActive() && !stc->AutoCompActive() ) {
    m_CallTipActive = wxSCI_INVALID_POSITION;
  }
  stc->AutoCompSetIgnoreCase( !isCaseSensitive );
  stc->AutoCompSetMaxHeight( 14 );
  stc->AutoCompSetTypeSeparator( _T( '\n' ) );
  stc->AutoCompSetSeparator( _T( '\r' ) );
  stc->AutoCompShow( tknEnd - tknStart, items );
  m_OwnsAutocomp = true;
  if( isPureAlphabetical ) {
    const wxString& contextStr = stc->GetTextRange( tknStart, stc->WordEndPosition( tknEnd, true ) );
    std::vector<cbCodeCompletionPlugin::CCToken>::const_iterator tknIt
      = std::lower_bound( m_AutocompTokens.begin(), m_AutocompTokens.end(),
                          cbCodeCompletionPlugin::CCToken( -1, contextStr ), sortFunctor );
    if( tknIt != m_AutocompTokens.end() && tknIt->displayName.StartsWith( contextStr ) ) {
      stc->AutoCompSelect( tknIt->displayName );
    }
  }
  m_LastACLaunchState[lsTknStart] = tknStart;
  m_LastACLaunchState[lsCaretStart] = tknEnd;
}

void CCManager::OnDeactivateApp( CodeBlocksEvent& event ) {
  DoHidePopup();
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( ed ) {
    M_Code_StyledTextCtrl* stc = ed->GetControl();
    if( stc->CallTipActive() ) {
      wxCommandEvent pendingCancel( cbEVT_DEFERRED_CALLTIP_CANCEL );
      AddPendingEvent( pendingCancel );
    }
    m_CallTipActive = wxSCI_INVALID_POSITION;
  }
  event.Skip();
}

void CCManager::OnDeactivateEd( CodeBlocksEvent& event ) {
  DoHidePopup();
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor( event.GetEditor() );
  if( ed ) {
    M_Code_StyledTextCtrl* stc = ed->GetControl();
    if( stc->CallTipActive() ) {
      stc->CallTipCancel();
    }
    m_CallTipActive = wxSCI_INVALID_POSITION;
  }
  event.Skip();
}

static void setupColours( M_Code_Editor *editor, ColourManager *manager ) {
  M_Code_StyledTextCtrl* stc = editor->GetControl();
  stc->CallTipSetBackground( manager->GetColour( _T( "cc_tips_back" ) ) );
  stc->CallTipSetForeground( manager->GetColour( _T( "cc_tips_fore" ) ) );
  stc->CallTipSetForegroundHighlight( manager->GetColour( _T( "cc_tips_highlight" ) ) );
}

void CCManager::OnEditorOpen( CodeBlocksEvent& event ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor( event.GetEditor() );
  if( ed ) {
    M_Code_StyledTextCtrl* stc = ed->GetControl();
    stc->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED,
                  wxListEventHandler( CCManager::OnAutocompleteSelect ), nullptr, this );
    setupColours( ed, Manager::Get()->GetColourManager() );
  }
}

void CCManager::UpdateEnvSettings() {
  EditorManager *editors = Manager::Get()->GetEditorManager();
  ColourManager* cmgr = Manager::Get()->GetColourManager();
  int count = editors->GetEditorsCount();
  for( int ii = 0; ii < count; ++ii ) {
    M_Code_Editor *editor = editors->GetBuiltinEditor( editors->GetEditor( ii ) );
    if( editor ) {
      setupColours( editor, cmgr );
    }
  }
}

void CCManager::OnEditorClose( CodeBlocksEvent& event ) {
  DoHidePopup();
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor( event.GetEditor() );
  if( ed == m_pLastEditor ) {
    m_pLastEditor = nullptr;
  }
  if( ed && ed->GetControl() ) {
    ed->GetControl()->Disconnect( wxEVT_COMMAND_LIST_ITEM_SELECTED,
                                  wxListEventHandler( CCManager::OnAutocompleteSelect ), nullptr, this );
  }
}

void CCManager::OnEditorTooltip( CodeBlocksEvent& event ) {
  event.Skip();
  int tooltipMode = Manager::Get()->GetConfigManager( _T( "ccmanager" ) )->ReadInt( _T( "/tooltip_mode" ), 1 );
  if( tooltipMode == tmDisable ) {
    return;
  }
  bool fromMouseDwell = event.GetString().IsEmpty();
  if( wxGetKeyState( WXK_CONTROL ) && fromMouseDwell ) {
    return;
  }
  if( tooltipMode == tmKeyboundOnly && fromMouseDwell ) {
    return;
  }
  EditorBase* base = event.GetEditor();
  M_Code_Editor* ed = base && base->IsBuiltinEditor() ? static_cast<M_Code_Editor*>( base ) : nullptr;
  if( !ed || ed->IsContextMenuOpened() ) {
    return;
  }
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  cbCodeCompletionPlugin* ccPlugin = GetProviderFor( ed );
  int pos = stc->PositionFromPointClose( event.GetX(), event.GetY() );
  if( !ccPlugin || pos < 0 || pos >= stc->GetLength() ) {
    if( stc->CallTipActive() && event.GetExtraLong() == 0 && m_CallTipActive == wxSCI_INVALID_POSITION ) {
      static_cast<wxScintilla*>( stc )->CallTipCancel();
    }
    return;
  }
  int hlStart, hlEnd, argsPos;
  hlStart = hlEnd = argsPos = wxSCI_INVALID_POSITION;
  bool allowCallTip = true;
  const std::vector<cbCodeCompletionPlugin::CCToken>& tokens = ccPlugin->GetTokenAt( pos, ed, allowCallTip );
  std::set<wxString> uniqueTips;
  for( size_t i = 0; i < tokens.size(); ++i ) {
    uniqueTips.insert( tokens[i].displayName );
  }
  wxStringVec tips( uniqueTips.begin(), uniqueTips.end() );
  const int style = event.GetInt();
  if( !tips.empty() ) {
    const int tknStart = stc->WordStartPosition( pos, true );
    const int tknEnd = stc->WordEndPosition( pos, true );
    if( tknEnd - tknStart > 2 ) {
      for( size_t i = 0; i < tips[0].Length(); ++i ) {
        size_t hlLoc = tips[0].find( stc->GetTextRange( tknStart, tknEnd ), i );
        if( hlLoc == wxString::npos ) {
          break;
        }
        hlStart = hlLoc;
        hlEnd = hlStart + tknEnd - tknStart;
        if( ( hlStart > 0 && ( tips[0][hlStart - 1] == _T( '_' ) || wxIsalpha( tips[0][hlStart - 1] ) ) )
            || ( hlEnd < static_cast<int>( tips[0].Length() ) - 1 && ( tips[0][hlEnd] == _T( '_' ) || wxIsalpha( tips[0][hlEnd] ) ) ) ) {
          i = hlEnd;
          hlStart = hlEnd = wxSCI_INVALID_POSITION;
        } else
        { break; }
      }
    }
  } else if( allowCallTip && !( stc->IsString( style ) || stc->IsComment( style ) || stc->IsCharacter( style ) || stc->IsPreprocessor( style ) ) ) {
    const int line = stc->LineFromPosition( pos );
    if( pos + 4 > stc->PositionFromLine( line ) + ( int )ed->GetLineIndentString( line ).Length() ) {
      const CallTipVec& cTips = ccPlugin->GetCallTips( pos, style, ed, argsPos );
      for( size_t i = 0; i < cTips.size(); ++i ) {
        tips.push_back( cTips[i].tip );
      }
      if( !tips.empty() ) {
        hlStart = cTips[0].hlStart;
        hlEnd = cTips[0].hlEnd;
      }
    }
  }
  if( tips.empty() ) {
    if( stc->CallTipActive() && event.GetExtraLong() == 0 && m_CallTipActive == wxSCI_INVALID_POSITION ) {
      static_cast<wxScintilla*>( stc )->CallTipCancel();
    }
  } else {
    DoShowTips( tips, stc, pos, argsPos, hlStart, hlEnd );
    event.SetExtraLong( 1 );
  }
  m_CallTipActive = wxSCI_INVALID_POSITION;
}

void CCManager::OnEditorHook( M_Code_Editor* ed, wxScintillaEvent& event ) {
  wxEventType evtType = event.GetEventType();
  if( evtType == wxEVT_SCI_CHARADDED ) {
    const wxChar ch = event.GetKey();
    CCPluginCharMap::const_iterator ctChars = m_CallTipChars.find( GetProviderFor( ed ) );
    if( ctChars == m_CallTipChars.end() ) {
      ctChars = m_CallTipChars.find( nullptr );
    }
    if( ctChars->second.find( ch ) != ctChars->second.end() ) {
      int tooltipMode = Manager::Get()->GetConfigManager( _T( "ccmanager" ) )->ReadInt( _T( "/tooltip_mode" ), 1 );
      if( tooltipMode != 3
          || m_CallTipActive != wxSCI_INVALID_POSITION ) {
        wxCommandEvent pendingShow( cbEVT_DEFERRED_CALLTIP_SHOW );
        AddPendingEvent( pendingShow );
      }
    } else {
      M_Code_StyledTextCtrl* stc = ed->GetControl();
      const int pos = stc->GetCurrentPos();
      const int wordStartPos = stc->WordStartPosition( pos, true );
      CCPluginCharMap::const_iterator alChars = m_AutoLaunchChars.find( GetProviderFor( ed ) );
      if( alChars == m_AutoLaunchChars.end() ) {
        alChars = m_AutoLaunchChars.find( nullptr );
      }
      int autolaunchCt = Manager::Get()->GetConfigManager( _T( "ccmanager" ) )->ReadInt( _T( "/auto_launch_count" ), 3 );
      if( ( pos - wordStartPos >= autolaunchCt && !stc->AutoCompActive() )
          || pos - wordStartPos == autolaunchCt + 4 ) {
        CodeBlocksEvent evt( cbEVT_COMPLETE_CODE );
        Manager::Get()->ProcessEvent( evt );
      } else if( alChars->second.find( ch ) != alChars->second.end() ) {
        m_AutoLaunchTimer.Start( 10, wxTIMER_ONE_SHOT );
        m_AutocompPosition = pos;
      }
    }
  } else if( evtType == wxEVT_SCI_UPDATEUI ) {
    if( event.GetUpdated() & ( wxSCI_UPDATE_V_SCROLL | wxSCI_UPDATE_H_SCROLL ) ) {
      M_Code_StyledTextCtrl* stc = ed->GetControl();
      if( stc->CallTipActive() ) {
        static_cast<wxScintilla*>( stc )->CallTipCancel();
        if( m_CallTipActive != wxSCI_INVALID_POSITION && CCManagerHelper::IsPosVisible( m_CallTipActive, stc ) ) {
          m_CallTipTimer.Start( SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT );
        }
      } else if( m_CallTipTimer.IsRunning() ) {
        if( CCManagerHelper::IsPosVisible( stc->GetCurrentPos(), stc ) ) {
          m_CallTipTimer.Start( SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT );
        } else {
          m_CallTipTimer.Stop();
          m_CallTipActive = wxSCI_INVALID_POSITION;
        }
      }
      if( m_AutoLaunchTimer.IsRunning() ) {
        if( CCManagerHelper::IsPosVisible( stc->GetCurrentPos(), stc ) ) {
          m_AutoLaunchTimer.Start( SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT );
        } else
        { m_AutoLaunchTimer.Stop(); }
      } else if( stc->AutoCompActive() ) {
        stc->AutoCompCancel();
        m_AutocompPosition = stc->GetCurrentPos();
        if( CCManagerHelper::IsPosVisible( m_AutocompPosition, stc ) ) {
          m_AutoLaunchTimer.Start( SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT );
        }
      }
    }
  } else if( evtType == wxEVT_SCI_KEY ) {
    M_Code_StyledTextCtrl* stc = ed->GetControl();
    switch( event.GetKey() ) {
      case wxSCI_KEY_LEFT:
      case wxSCI_KEY_RIGHT:
        if( !stc->CallTipActive() && !stc->AutoCompActive() ) {
          m_CallTipActive = wxSCI_INVALID_POSITION;
        }
      case wxSCI_KEY_UP:
      case wxSCI_KEY_DOWN:
        if( m_CallTipActive != wxSCI_INVALID_POSITION && !stc->AutoCompActive() ) {
          m_CallTipTimer.Start( CALLTIP_REFRESH_DELAY, wxTIMER_ONE_SHOT );
        }
        break;
      default:
        break;
    }
  } else if( evtType == wxEVT_SCI_MODIFIED ) {
    if( event.GetModificationType() & wxSCI_PERFORMED_UNDO ) {
      M_Code_StyledTextCtrl* stc = ed->GetControl();
      if( m_CallTipActive != wxSCI_INVALID_POSITION && stc->GetCurrentPos() >= m_CallTipActive ) {
        m_CallTipTimer.Start( CALLTIP_REFRESH_DELAY, wxTIMER_ONE_SHOT );
      } else
      { static_cast<wxScintilla*>( stc )->CallTipCancel(); }
    }
  } else if( evtType == wxEVT_SCI_AUTOCOMP_SELECTION ) {
    DoHidePopup();
    cbCodeCompletionPlugin* ccPlugin = GetProviderFor( ed );
    if( ccPlugin && m_OwnsAutocomp ) {
      if( m_LastAutocompIndex != wxNOT_FOUND
          && m_LastAutocompIndex < ( int )m_AutocompTokens.size() ) {
        ccPlugin->DoAutocomplete( m_AutocompTokens[m_LastAutocompIndex], ed );
      } else
      { ccPlugin->DoAutocomplete( event.GetText(), ed ); }
      CallSmartIndentCCDone( ed );
    }
  } else if( evtType == wxEVT_SCI_AUTOCOMP_CANCELLED ) {
    DoHidePopup();
  } else if( evtType == wxEVT_SCI_CALLTIP_CLICK ) {
    switch( event.GetPosition() ) {
      case 1:
        AdvanceTip( Previous );
        DoUpdateCallTip( ed );
        break;
      case 2:
        AdvanceTip( Next );
        DoUpdateCallTip( ed );
        break;
      case 0:
      default:
        break;
    }
  }
  event.Skip();
}

void CCManager::OnShowCallTip( CodeBlocksEvent& event ) {
  event.Skip();
  int tooltipMode = Manager::Get()->GetConfigManager( _T( "ccmanager" ) )->ReadInt( _T( "/tooltip_mode" ), 1 );
  if( tooltipMode == tmDisable ) {
    return;
  }
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !ed ) {
    return;
  }
  cbCodeCompletionPlugin* ccPlugin = GetProviderFor( ed );
  if( !ccPlugin ) {
    return;
  }
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  if( !stc ) {
    return;
  }
  int pos = stc->GetCurrentPos();
  int argsPos = wxSCI_INVALID_POSITION;
  wxString curTip;
  if( !m_CallTips.empty() && m_CurCallTip != m_CallTips.end() ) {
    curTip = m_CurCallTip->tip;
  }
  m_CallTips = ccPlugin->GetCallTips( pos, stc->GetStyleAt( pos ), ed, argsPos );
  if( !m_CallTips.empty() && ( event.GetInt() != FROM_TIMER || argsPos == m_CallTipActive ) ) {
    int lnStart = stc->PositionFromLine( stc->LineFromPosition( pos ) );
    while( wxIsspace( stc->GetCharAt( lnStart ) ) ) {
      ++lnStart;
    }
    if( m_CallTips.size() > 1 && tooltipMode == tmForceSinglePage ) {
      wxString tip;
      int hlStart, hlEnd;
      hlStart = hlEnd = wxSCI_INVALID_POSITION;
      for( CallTipVec::const_iterator itr = m_CallTips.begin();
           itr != m_CallTips.end(); ++itr ) {
        if( hlStart == hlEnd && itr->hlStart != itr->hlEnd ) {
          hlStart = tip.Length() + itr->hlStart;
          hlEnd = tip.Length() + itr->hlEnd;
        }
        tip += itr->tip + _T( '\n' );
      }
      m_CallTips.clear();
      m_CallTips.push_back( cbCodeCompletionPlugin::CCCallTip( tip.RemoveLast(), hlStart, hlEnd ) );
    }
    m_CurCallTip = m_CallTips.begin();
    if( m_CallTips.size() > 1 ) {
      std::map<int, size_t>::const_iterator choiceItr =
        m_CallTipChoiceDict.find( CCManagerHelper::CallTipToInt( m_CurCallTip->tip, m_CallTips.size() ) );
      if( choiceItr != m_CallTipChoiceDict.end() && choiceItr->second < m_CallTips.size() ) {
        m_CurCallTip = m_CallTips.begin() + choiceItr->second;
      }
      if( choiceItr == m_CallTipChoiceDict.end() || argsPos == m_CallTipActive ) {
        int prefixEndPos = argsPos;
        while( prefixEndPos > 0 && wxIsspace( stc->GetCharAt( prefixEndPos - 1 ) ) ) {
          --prefixEndPos;
        }
        const wxString& prefix = stc->GetTextRange( stc->WordStartPosition( prefixEndPos, true ), prefixEndPos );
        choiceItr = m_CallTipFuzzyChoiceDict.find( CCManagerHelper::CallTipToInt( prefix, m_CallTips.size() ) );
        if( choiceItr != m_CallTipFuzzyChoiceDict.end() && choiceItr->second < m_CallTips.size() ) {
          m_CurCallTip = m_CallTips.begin() + choiceItr->second;
        }
      }
      for( CallTipVec::const_iterator itr = m_CallTips.begin();
           itr != m_CallTips.end(); ++itr ) {
        if( itr->tip == curTip ) {
          m_CurCallTip = itr;
          break;
        }
      }
    }
    m_CallTipActive = argsPos;
    DoUpdateCallTip( ed );
  } else {
    if( m_CallTipActive != wxSCI_INVALID_POSITION ) {
      static_cast<wxScintilla*>( stc )->CallTipCancel();
      m_CallTipActive = wxSCI_INVALID_POSITION;
    }
    m_CurCallTip = m_CallTips.end();
  }
}

void CCManager::OnAutocompleteSelect( wxListEvent& event ) {
  event.Skip();
  m_AutocompSelectTimer.Start( AUTOCOMP_SELECT_DELAY, wxTIMER_ONE_SHOT );
  wxObject* evtObj = event.GetEventObject();
  if( !evtObj ) {
    return;
  }
  wxWindow* evtWin = static_cast<wxWindow*>( evtObj )->GetParent();
  if( !evtWin ) {
    return;
  }
  m_DocPos = m_pPopup->GetParent()->ScreenToClient( evtWin->GetScreenPosition() );
  m_DocPos.x += evtWin->GetSize().x;
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  wxRect edRect = ed->GetRect();
  if( !m_pPopup->IsShown() ) {
    M_Code_StyledTextCtrl* stc = ed->GetControl();
    int acMaxHeight = stc->AutoCompGetMaxHeight() + 1;
    int textHeight = stc->TextHeight( stc->GetCurrentLine() );
    m_DocSize.x = edRect.width * 5 / 12;
    m_DocSize.y = acMaxHeight * textHeight;
    evtWin->Connect( wxEVT_SHOW, wxShowEventHandler( CCManager::OnAutocompleteHide ), nullptr, this );
    const int idx = wxDisplay::GetFromWindow( evtWin );
    m_WindowBound = m_DocPos.x + m_DocSize.x;
    if( idx != wxNOT_FOUND ) {
      const wxPoint& corner = m_pPopup->GetParent()->ScreenToClient( wxDisplay( idx ).GetGeometry().GetBottomRight() );
      m_DocSize.y = std::max( 9 * textHeight, std::min( m_DocSize.y, corner.y - m_DocPos.y - 2 ) );
      m_DocSize.x = std::max( m_DocSize.y * 2 / 3, std::min( m_DocSize.x, corner.x - m_DocPos.x - 2 ) );
      m_WindowBound = std::min( corner.x - 2, m_WindowBound );
    }
  }
  if( ( m_DocPos.x + m_DocSize.x ) > m_WindowBound ) {
    m_DocPos.x -= evtWin->GetSize().x + m_DocSize.x;
  } else {
    m_DocSize.x = std::min( m_WindowBound - m_DocPos.x, edRect.width * 5 / 12 );
  }
}

void CCManager::OnAutocompleteHide( wxShowEvent& event ) {
  event.Skip();
  DoHidePopup();
  wxObject* evtObj = event.GetEventObject();
  if( evtObj ) {
    static_cast<wxWindow*>( evtObj )->Disconnect( wxEVT_SHOW, wxShowEventHandler( CCManager::OnAutocompleteHide ), nullptr, this );
  }
  if( m_CallTipActive != wxSCI_INVALID_POSITION && !m_AutoLaunchTimer.IsRunning() ) {
    m_CallTipTimer.Start( CALLTIP_REFRESH_DELAY, wxTIMER_ONE_SHOT );
  }
}

void CCManager::OnDeferredCallTipShow( wxCommandEvent& event ) {
  CodeBlocksEvent evt( cbEVT_SHOW_CALL_TIP );
  evt.SetInt( event.GetInt() );
  Manager::Get()->ProcessEvent( evt );
}

void CCManager::OnDeferredCallTipCancel( wxCommandEvent& ( event ) ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( ed ) {
    static_cast<wxScintilla*>( ed->GetControl() )->CallTipCancel();
  }
}

void CCManager::OnHtmlLink( wxHtmlLinkEvent& event ) {
  cbCodeCompletionPlugin* ccPlugin = GetProviderFor();
  if( !ccPlugin ) {
    return;
  }
  bool dismissPopup = false;
  const wxString& html = ccPlugin->OnDocumentationLink( event, dismissPopup );
  if( dismissPopup ) {
    DoHidePopup();
  } else if( !html.IsEmpty() ) {
    m_pHtml->SetPage( html );
  }
}

void CCManager::OnTimer( wxTimerEvent& event ) {
  if( event.GetId() == idCallTipTimer ) {
    wxCommandEvent evt( cbEVT_DEFERRED_CALLTIP_SHOW );
    evt.SetInt( FROM_TIMER );
    AddPendingEvent( evt );
  } else if( event.GetId() == idAutoLaunchTimer ) {
    M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
    if( ed && ed->GetControl()->GetCurrentPos() == m_AutocompPosition ) {
      CodeBlocksEvent evt( cbEVT_COMPLETE_CODE );
      evt.SetInt( FROM_TIMER );
      Manager::Get()->ProcessEvent( evt );
    }
    m_AutocompPosition = wxSCI_INVALID_POSITION;
  } else if( event.GetId() == idAutocompSelectTimer ) {
    M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
    if( ed ) {
      M_Code_StyledTextCtrl* stc = ed->GetControl();
      if( stc->AutoCompActive() ) {
        m_LastAutocompIndex = stc->AutoCompGetCurrent();
        DoShowDocumentation( ed );
      }
    }
  } else {
    event.Skip();
  }
}

void CCManager::OnMenuSelect( wxCommandEvent& event ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !ed ) {
    return;
  }
  if( event.GetId() == idShowTooltip ) {
    M_Code_StyledTextCtrl* stc = ed->GetControl();
    CodeBlocksEvent evt( cbEVT_EDITOR_TOOLTIP );
    wxPoint pt = stc->PointFromPosition( stc->GetCurrentPos() );
    evt.SetX( pt.x );
    evt.SetY( pt.y );
    evt.SetInt( stc->GetStyleAt( stc->GetCurrentPos() ) );
    evt.SetEditor( ed );
    evt.SetExtraLong( 0 );
    evt.SetString( _T( "evt from menu" ) );
    Manager::Get()->ProcessEvent( evt );
    return;
  }
  if( m_CallTips.empty() || m_CallTipActive == wxSCI_INVALID_POSITION ) {
    return;
  }
  if( !ed->GetControl()->CallTipActive() ) {
    return;
  }
  if( event.GetId() == idCallTipNext ) {
    AdvanceTip( Next );
    DoUpdateCallTip( ed );
  } else if( event.GetId() == idCallTipPrevious ) {
    AdvanceTip( Previous );
    DoUpdateCallTip( ed );
  }
}

void CCManager::DoBufferedCC( M_Code_StyledTextCtrl* stc ) {
  if( stc->AutoCompActive() ) {
    return;
  }
  wxString items;
  items.Alloc( m_AutocompTokens.size() * 20 );
  for( size_t i = 0; i < m_AutocompTokens.size(); ++i ) {
    items += m_AutocompTokens[i].displayName;
    if( m_AutocompTokens[i].category == -1 ) {
      items += _T( "\r" );
    } else
    { items += F( _T( "\n%d\r" ), m_AutocompTokens[i].category ); }
  }
  items.RemoveLast();
  if( !stc->CallTipActive() ) {
    m_CallTipActive = wxSCI_INVALID_POSITION;
  }
  stc->AutoCompShow( m_LastACLaunchState[lsCaretStart] - m_LastACLaunchState[lsTknStart], items );
  m_OwnsAutocomp = true;
  if( m_LastAutocompIndex != wxNOT_FOUND
      && m_LastAutocompIndex < ( int )m_AutocompTokens.size() ) {
    const cbCodeCompletionPlugin::CCToken& token = m_AutocompTokens[m_LastAutocompIndex];
    const int sepIdx = token.displayName.Find( '\n', true );
    if( sepIdx == wxNOT_FOUND ) {
      stc->AutoCompSelect( token.displayName );
    } else
    { stc->AutoCompSelect( token.displayName.Mid( 0, sepIdx ) ); }
  }
}

void CCManager::DoHidePopup() {
  if( !m_pPopup->IsShown() ) {
    return;
  }
  m_pPopup->Hide();
}

void CCManager::DoShowDocumentation( M_Code_Editor* ed ) {
  if( !Manager::Get()->GetConfigManager( _T( "ccmanager" ) )->ReadBool( _T( "/documentation_popup" ), true ) ) {
    return;
  }
  cbCodeCompletionPlugin* ccPlugin = GetProviderFor( ed );
  if( !ccPlugin ) {
    return;
  }
  if( m_LastAutocompIndex == wxNOT_FOUND
      || m_LastAutocompIndex >= ( int )m_AutocompTokens.size() ) {
    return;
  }
  const wxString& html = ccPlugin->GetDocumentation( m_AutocompTokens[m_LastAutocompIndex] );
  if( html.IsEmpty() ) {
    DoHidePopup();
    return;
  }
  m_pPopup->Freeze();
  m_pHtml->SetSize( m_DocSize );
  m_pHtml->SetPage( html );
  m_pPopup->SetClientSize( m_DocSize );
  m_pPopup->SetPosition( m_DocPos );
  m_pPopup->Thaw();
  if( !m_pPopup->IsShown() ) {
    m_pPopup->Show();
  }
}

void CCManager::DoUpdateCallTip( M_Code_Editor* ed ) {
  wxStringVec tips;
  int hlStart = m_CurCallTip->hlStart;
  int hlEnd = m_CurCallTip->hlEnd;
  size_t sRange = 0;
  size_t eRange = m_CurCallTip->tip.find( _T( '\n' ) );
  while( eRange != wxString::npos ) {
    tips.push_back( m_CurCallTip->tip.Mid( sRange, eRange - sRange ) );
    CCManagerHelper::RipplePts( hlStart, hlEnd, eRange, -1 );
    sRange = eRange + 1;
    eRange = m_CurCallTip->tip.find( _T( '\n' ), sRange );
  }
  if( sRange < m_CurCallTip->tip.Length() ) {
    tips.push_back( m_CurCallTip->tip.Mid( sRange ) );
  }
  int offset = 0;
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  if( m_CallTips.size() > 1 ) {
    tips.front().Prepend( _T( "\001\002" ) );
    offset += 2;
    wxString tip;
    tip << _T( "(" ) << ( m_CurCallTip - m_CallTips.begin() + 1 ) << _T( "/" ) << m_CallTips.size() << _T( ")" );
    tips.push_back( tip );
    m_CallTipChoiceDict[CCManagerHelper::CallTipToInt( m_CallTips.front().tip, m_CallTips.size() )] = m_CurCallTip - m_CallTips.begin();
    int prefixEndPos = m_CallTipActive;
    while( prefixEndPos > 0 && wxIsspace( stc->GetCharAt( prefixEndPos - 1 ) ) ) {
      --prefixEndPos;
    }
    const wxString& prefix = stc->GetTextRange( stc->WordStartPosition( prefixEndPos, true ), prefixEndPos );
    m_CallTipFuzzyChoiceDict[CCManagerHelper::CallTipToInt( prefix, m_CallTips.size() )] = m_CurCallTip - m_CallTips.begin();
  }
  int pos = stc->GetCurrentPos();
  int lnStart = stc->PositionFromLine( stc->LineFromPosition( pos ) );
  while( wxIsspace( stc->GetCharAt( lnStart ) ) ) {
    ++lnStart;
  }
  DoShowTips( tips, stc, std::max( pos, lnStart ), m_CallTipActive, hlStart + offset, hlEnd + offset );
}

void CCManager::DoShowTips( const wxStringVec& tips, M_Code_StyledTextCtrl* stc, int pos, int argsPos, int hlStart, int hlEnd ) {
  int maxLines = std::max( stc->LinesOnScreen() / 4, 5 );
  int marginWidth = stc->GetMarginWidth( wxSCI_MARGIN_SYMBOL ) + stc->GetMarginWidth( wxSCI_MARGIN_NUMBER );
  int maxWidth = ( stc->GetSize().x - marginWidth ) / stc->TextWidth( wxSCI_STYLE_LINENUMBER, _T( "W" ) ) - 1;
  maxWidth = std::min( std::max( 60, maxWidth ), 135 );
  wxString tip;
  int lineCount = 0;
  wxString lineBreak = _T( '\n' );
  if( !tips.front().IsEmpty() && tips.front()[0] <= _T( '\002' ) ) {
    lineBreak += _T( ' ' );
    if( tips.front().Length() > 1 && tips.front()[1] <= _T( '\002' ) ) {
      lineBreak += _T( "  " );
    }
  }
  for( size_t i = 0; i < tips.size() && lineCount < maxLines; ++i ) {
    if( tips[i].Length() > ( size_t )maxWidth + 6 ) {
      wxString tipLn = tips[i];
      while( !tipLn.IsEmpty() ) {
        wxString segment = tipLn.Mid( 0, maxWidth );
        int index = segment.Find( _T( ' ' ), true );
        if( index < 20 ) {
          segment = tipLn.Mid( 0, maxWidth * 6 / 5 );
          index = segment.Find( _T( ' ' ), true );
        }
        for( int commaIdx = index - 1; commaIdx > maxWidth / 2; --commaIdx ) {
          if( segment[commaIdx] == _T( ',' ) && segment[commaIdx + 1] == _T( ' ' ) ) {
            index = commaIdx + 1;
            break;
          }
        }
        if( index < 20 || segment == tipLn ) {
          tip += tipLn + lineBreak;
          CCManagerHelper::RipplePts( hlStart, hlEnd, tip.Length(), lineBreak.Length() );
          tipLn.Clear();
        } else {
          tip += segment.Mid( 0, index ) + lineBreak + _T( ' ' );
          CCManagerHelper::RipplePts( hlStart, hlEnd, tip.Length(), lineBreak.Length() + 1 );
          tipLn = tipLn.Mid( index );
        }
        ++lineCount;
      }
    } else {
      tip += tips[i] + lineBreak;
      CCManagerHelper::RipplePts( hlStart, hlEnd, tip.Length(), lineBreak.Length() );
      ++lineCount;
    }
  }
  tip.Trim();
  int line = stc->LineFromPosition( pos );
  if( argsPos == wxSCI_INVALID_POSITION ) {
    argsPos = stc->WordStartPosition( pos, true );
  } else {
    argsPos = std::min( CCManagerHelper::FindColumn( line, stc->GetColumn( argsPos ), stc ), stc->WordStartPosition( pos, true ) );
  }
  int offset = stc->PointFromPosition( stc->PositionFromLine( line ) ).x > marginWidth ? 0 : 2;
  pos = std::max( argsPos, stc->PositionFromPoint( wxPoint( marginWidth, stc->PointFromPosition( pos ).y ) ) + offset );
  pos = std::min( pos, stc->GetLineEndPosition( line ) );
  if( stc->CallTipActive() && m_LastTipPos != pos ) {
    stc->CallTipCancel();
  }
  stc->CallTipShow( pos, tip );
  if( hlStart >= 0 && hlEnd > hlStart ) {
    stc->CallTipSetHighlight( hlStart, hlEnd );
  }
  m_LastTipPos = pos;
}

void CCManager::CallSmartIndentCCDone( M_Code_Editor* ed ) {
  CodeBlocksEvent event( cbEVT_EDITOR_CC_DONE );
  event.SetEditor( ed );
  Manager::Get()->ProcessEvent( event );
}
